// Copyright  2004-2008 Backplane Ltd.
//
// This file is part of Ubiquity formsPlayer
//
// Ubiquity formsPlayer is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3 of the License, or (at your
// option) any later version.
//
// Ubiquity formsPlayer is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License
// along with Ubiquity formsPlayer If not, see <http://www.gnu.org/licenses/>.
//	
//
//   Purpose: implementation of CImplRepeat.
//   $Id$


#include "stdafx.h"
#include "repeat.h"
#include "implGroup.h"
#include "IDResolver.h"
#include "Model.h"
#include "RepeatUI.h"



//////////////////////////////////////////////////////////////////////
// CRepeat Class
//////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CImplRepeat::CImplRepeat(CXFormsElementRepeat* pElement)
	: CImplNodesetBinding((CXFormsElementNodesetBinding*) pElement)
	,m_ulInitialStartIndex(1)
	,m_ulNumber(0)
	,m_bCursorRequested(false)
	,m_bIsResizeable(true)
	,m_ulStartIndex(1)

{
	m_bisBound = true;
//	m_pReliantControls = new CTypedPtrList<CPtrList, CImplNodeBinding*>;
	CFormControl::SetControlType(L"<repeat>");
}
#define _DEBUG_CATCH_REPEAT_ITERATION_DELETION_AT_SOURCE
#undef _DEBUG_CATCH_REPEAT_ITERATION_DELETION_AT_SOURCE
CImplRepeat::~CImplRepeat()
{
	for (long l = 0; l <=m_Iterations.GetUpperBound();++l)
	{
#ifndef _DEBUG_CATCH_REPEAT_ITERATION_DELETION_AT_SOURCE
		try
		{
#endif 
			delete m_Iterations[l];
#ifndef _DEBUG_CATCH_REPEAT_ITERATION_DELETION_AT_SOURCE
		}
		catch(...){ASSERT(0);}
#endif
	}
	m_Iterations.RemoveAll();
}

HRESULT CImplRepeat::RemoveChild(CImpl * pChild)
{

	HRESULT hr = __super::RemoveChild(pChild);

	for(int i = 0; i <= m_Iterations.GetUpperBound(); i++)
	{
		CImplGroup * pCurGroup = dynamic_cast<CImplGroup *> (m_Iterations.GetAt(i));
		if(pCurGroup == pChild)
		{
			m_Iterations.SetAt(i, 0);
			return S_OK;
		}
	}

	return hr;
}

ULONG CImplRepeat::getCursor()
{
	m_bCursorRequested = true;
	return __super::getCursor();
}

HRESULT CImplRepeat::init(CImpl * pParent)
{
	HRESULT hr = S_OK;
	EnterDeletionPostponementState();
	
	try
	{
	
		//set the numeric members, all are initially the same,
		//	the first item in the list is selected
		m_ulCursor = m_ulStartIndex = m_ulScrollOffset = m_ulInitialStartIndex;
		//scrolloffset is a 0 start numerator
		--m_ulScrollOffset;
		
		//Set the resizeable property, For now, if m_ulNumber is still 0 after parsing.
		//	set the repeat to resizable.  The problem is that a punter might want to set
		//	a repeat to display 0 iterations, possibly for debugging purposes.
		//	There is no way, using the current parsing technique, to determine 
		//	whether a value has been set or not.

		m_bIsResizeable = (m_ulNumber == 0)?true:false;
		hr = CImplNodesetBinding::init(pParent);
	}
	catch(...)
	{
		ASSERT(0);
	}

	try
	{
		if(SUCCEEDED(hr) && !IsAwaitingDeletion())
		{
			//A repeat's id resolver is really its child, so get the parent's 
			// id resolver in order to add to the correct scope.
			CIdResolver * pResolver = 0;
			if(m_pParentImpl)
			{
				pResolver = m_pParentImpl->GetIdResolver();
			}
			else if(m_pXForms)
			{
				pResolver = &(m_pXForms->m_IdResolver);
			}

			if(pResolver)
				pResolver->AddItem(this);
		}
	}
	catch(...)
	{
		ASSERT(0);
	}
	ExitDeletionPostponementState();
	
	return hr;
}


//Removed, unused
//GetContextNode fully removed in version 139 

HRESULT CImplRepeat::onscrollFirst()
{
	return FireEvent(EVENT_SCROLLFIRST);
}

HRESULT CImplRepeat::onscrollLast()
{
	return FireEvent(EVENT_SCROLLLAST);
}



HRESULT CImplRepeat::setCursor(unsigned long ulCursor)
{

	// allows sensible internal access to setting
	//	the cursor.
	

	 // Set the range for the cursor
 
	HRESULT hr = S_FALSE;
	//If I fail to get any nodes for it, upper and lower are 0;
	ULONG ulLower = 1;
	ULONG ulUpper = 1;

	ulUpper = static_cast<unsigned long>(m_vertices.GetSize());

	 //Limit the cursor to the range
 

	if (ulCursor <= ulLower)
		ulCursor = ulLower;
	else if (ulCursor > ulUpper)
		ulCursor = ulUpper;
	if(ulCursor == 0)
		ulCursor = 1;

	//if the cursor has not changed, don't do anything.
	if(ulCursor != m_ulCursor)
	{
		 // If the cursor hit the upper or lower limits
		//	then fire the scroll events

		if (ulCursor == ulLower)
			hr = onscrollFirst();
		//this is not an else condition, if a repeat has one 
		//	iteration, it can simultaneously scroll to first and last.
		if (ulCursor == ulUpper)
			hr = onscrollLast();

		// dehighlight the current iteration.
		long lIterationIndex = m_ulCursor - m_ulScrollOffset - 1;
		if(lIterationIndex >=0 && lIterationIndex < m_Iterations.GetSize())
			m_Iterations[lIterationIndex]->SetAsRepeatIndex(false);
			//static_cast<CXFormsElementGroup*>(m_Iterations[lIterationIndex]->m_pXFormsElement)->highlight(false);

		//set the cursor to this tested and restricted value.
		m_ulCursor = ulCursor; 
		//set the scrolloffset to be allow us to see the current cursor

		if(ulCursor <= m_ulScrollOffset)
		{
			//if it is before the first visible repetition, it
			//	becomes the first visible repetition.
			m_ulScrollOffset = ulCursor - 1;
		}
		else if(!m_bIsResizeable && m_ulScrollOffset + m_ulNumber < ulCursor)
		{
	//		if it is after the last visible repetition, 
	//			it becomes the last visible repetition.
			m_ulScrollOffset = (ulCursor - m_ulNumber);
		}
		//highlight the new current iteration
		lIterationIndex = m_ulCursor - m_ulScrollOffset - 1;
		if(lIterationIndex >=0 && m_Iterations.GetSize() >  lIterationIndex)
			m_Iterations[lIterationIndex]->SetAsRepeatIndex(true);
		
		if(rebuildOnIndexChange())
		{
			//I think rebuild and recalculate should happen here, but I'm not sure.
			m_pXForms->IncreaseDeferredUpdateCnt();
			hr = m_pModel->rebuild(true);
			hr = m_pModel->recalculate(true);
			hr = m_pModel->refresh(true);
			m_pXForms->DecreaseDeferredUpdateCnt();
		}
	}
	
	return hr;
	
}
HRESULT CImplRepeat::setCursor(BSTR sCursor)
{
	//cursor = XPath expression that evaluates to number -
	//			required 1-based offset into the sequence.

	HRESULT hr = S_OK;

	if (S_OK == hr)
	{

		//Evaluate the XPath to get a number 
		//	to which to set the cursor.

		double dblData;
		//Whether we use this->GetIdResolver or m_pParentImpl->GetIdResolver
		//	doesn't really matter here?
		CIdResolver * pResolver = 0;
		if(m_pParentImpl)
			pResolver =  m_pParentImpl->GetIdResolver();
		else
			pResolver = &(m_pXForms->m_IdResolver);

		m_pXForms->putXPathIdentityResolver(pResolver);
		hr = m_pModel->getInstanceNumber(sCursor, &dblData);
		m_pXForms->putXPathIdentityResolver(0);

		if (S_OK == hr)
		{
			//convert the Number down, this may lead to problems, but
			//	I doubt if we will be using numbers outside the range

			//TODO:
			//if 0, potential NaN, should perform test to
			//	see if 0 was required, then do nothing or
			//	set to 1 whichever is appropriate.

			ULONG ulCursor = static_cast<unsigned long>(dblData);
			//	call numeric access to setCursor.
			hr = setCursor(ulCursor);
		}
	}//if ( binding was retrieved OK )

	return hr;
}

HRESULT CImplRepeat::AddChild(CImpl *pChild)
{
	
	long l = pChild->getParentOrdinal();
	if(l > -1 && l < m_Iterations.GetCount())
	{
		//If the child has parentOrdinal, fetch the appropriate group
		//	and add it to that.
		CImplGroup * pIteration = m_Iterations.GetAt(l);
		if(pIteration)
		{
			pIteration->AddChild(pChild);
			pChild->m_pParentImpl = pIteration;
		}
	}
	else
	{
		//If child is a group, then add it to the list of iterations.
		CImplGroup * pGroup = dynamic_cast<CImplGroup*>(pChild);
		if(pGroup)
		{
			long lIndex = m_Iterations.Add(pGroup);
			//tell the group that it is a repeat iteration.
			pGroup->SetAsRepeatIteration(lIndex);
			unsigned long lSize = m_vertices.GetSize();
			if(lIndex + m_ulScrollOffset < lSize)
			{
				CVertex * vx = m_vertices.GetAt(lIndex + m_ulScrollOffset);
				pGroup->m_vertices.SetAtGrow(0,vx);
				vx->m_pInstanceNode->addTarget(pGroup);	
			}
			
			//if this is the current cursor, highlight it:
			if(m_ulCursor - m_ulScrollOffset - 1 == static_cast<unsigned long>(lIndex))
				pGroup->SetAsRepeatIndex(true);

		}
	}
	return S_OK;
}

HRESULT CImplRepeat::refresh()
{
	//doing this ensures that our cursor is still within the 
	//	range of the nodeset represented by repeat.  Even when 
	//	content is deleted to a count below the current offset.
	HRESULT hr = setCursor(m_ulCursor);

	POSITION pos = m_ReliantControls.GetHeadPosition();
	while(pos)
	{
		hr = m_ReliantControls.GetNext(pos)->refresh();
		if(FAILED(hr))
			break;
	}

	if(SUCCEEDED(hr))
	{
		//Highlight the current cursor on screen
		if
		(
			m_ulCursor > m_ulScrollOffset
			&& 
				static_cast<unsigned long>(m_Iterations.GetSize())
				> 
				m_ulCursor - m_ulScrollOffset
		)
		{
			CImplGroup * pIteration = m_Iterations[m_ulCursor - m_ulScrollOffset - 1];
			if(pIteration)
				pIteration->SetAsRepeatIndex(true);
		}
	}
	FireStateEvents();

	return hr;
}


HRESULT  CImplRepeat::decrementIndex()
{
	return setCursor(m_ulCursor - 1);
}
HRESULT  CImplRepeat::incrementIndex()
{
	return setCursor(m_ulCursor + 1);
}

HRESULT CImplRepeat::refreshIterationBindings()
{
	
	return S_OK;
}



CIdResolver * CImplRepeat::GetIdResolver() const
{
	//return the correct resolver for the 
	//	cursor.
	//m_ulCursor is a 1 based index, m_iterations starts at 0;
	if(m_Iterations.GetSize() > static_cast<long>(m_ulCursor - m_ulScrollOffset - 1))
	{
		CImplGroup * pIteration = 0;
		pIteration = m_Iterations.GetAt(m_ulCursor - m_ulScrollOffset - 1);

		//if we have a group with the requested iteration, return it
		//	This is highly unlikely to fail, 
		//	but the fallbacks are there just in case.
		if(pIteration)
			return pIteration->GetIdResolver();

	}
	//otherwise, use the conventional path to fetch an id resolver
	if(m_pParentImpl)
		return m_pParentImpl->GetIdResolver();
	else if(m_pXForms)
		return &(m_pXForms->m_IdResolver);
	else 
		return 0;
}

#define IGNORE_M_ITERATIONS_INITIALIZE_ERROR 1
HRESULT CImplRepeat::doInitialize()
{
	if(!m_bIsResizeable && m_pXFormsElement)
	{
		dynamic_cast<CXFormsElementRepeat*>(m_pXFormsElement)->put_iterations(m_ulNumber);
	}
	//TODO: this is generic for all objects that define their own
	//	identifier scope, since that only includes repeats for now
	//	it is fine here, but if another element starts to define 
	//	its own scope, we should generalise.

	//Now that all controls have been added to the repeat, 
	//	I need to expose the id resolution scope to the parentimpl 
	//	or top level.

	//TODO: At this point, the children's immediate scope is 
	//	full, but the nested scope is not necessarily 
	//	initialised.
	long len = m_Iterations.GetSize();
	long l;
	//temporarily set 	m_bInitializeDone to true, so that iterations actually get initialised here.;
//	bool bInitializeDone = m_bInitializeDone;
//	m_bInitializeDone = true;
	for(l = 0; l < len ;++l)
	{
		//If the repeat is being initialised, 
		//	the iterations must be as well, even if
		//	they have been initialized before (eg. submit)
		try
		{
			m_Iterations[l]->m_bInitializeDone = false;
			m_Iterations[l]->initialize(true);
		}
		catch(...)
		{
			ASSERT(IGNORE_M_ITERATIONS_INITIALIZE_ERROR);
		}
	}
//	m_bInitializeDone =bInitializeDone;
	CIdResolver * pResolver = GetIdResolver();
	if(pResolver)
	{
		CIdResolver * pParentResolver = 0;
		if(m_pParentImpl)
		{
			pParentResolver = m_pParentImpl->GetIdResolver();
		}
		else
		{
			pParentResolver = &(m_pXForms->m_IdResolver);
		}
		if(pParentResolver)
		{
			pResolver->ResetIterator();
			//iterate through the child ids until
			//	there are no more
			CComBSTR sScopedId;
			while(pResolver->IterateIds(&sScopedId))
			{
				pParentResolver->AddScopedId(sScopedId,this);
				sScopedId.Empty(); // Bug 211: CComBSTR::operator& returns the address of m_str but
				                   //          does not free existing memory, so this loop was leaking
				                   //          with each iteration.
			}
		}
		//reset, just in case anyone is interested
		pResolver->ResetIterator();
	}

	//buildDependencies();
	//pass up for default initialisation
	HRESULT hr = CImplNodesetBinding::doInitialize();
	return hr;
}

HRESULT CImplRepeat::setCursor(IXMLDOMNode *pNode)
{
	HRESULT hr;
	if(pNode)
	{
		bool bFound = false;
		for (int i=0; i < m_vertices.GetSize();++i)
		{
			if(m_vertices.GetAt(i)->m_pInstanceNode)
			{
				if(m_vertices.GetAt(i)->m_pInstanceNode->isSameNode(pNode))
				{
					bFound = true;
					break;
				}
			}
		}

		if(bFound)
			hr = setCursor(i + 1);
		else
			hr = E_FAIL;
	}
	else
	{
		hr = E_FAIL;
	}

	//m_vertices is 0-based, setCursor works on a list starting at 1.
	return hr;
}

#define AWAIT_NEW_ITERATIONS_MSG 1

void CImplRepeat::buildDependencies()
{
	CImplNodesetBinding::buildDependencies();
	setCursor(m_ulCursor);
	long lUBound = m_Iterations.GetUpperBound();
	//This is handled in CImplGroup::buildDependencies.
	/*
	for(l=0;l <= lUBound && l <= lNodeUBound;++l)
	{
		CVertex * pVx = m_vertices.GetAt(l + m_ulScrollOffset);
		pVx->m_pInstanceNode->addTarget(m_Iterations[l]);
		m_Iterations[l]->m_vertices.SetAtGrow(0,pVx );
	}
	*/
	//TODO:
	//I think this is also handled there, but I'm not sure
	//	develop a test, and work out whether this is needed
	//	or not, then remove it.
	if(lUBound >=0)
	{
		//If there are any iterations,
		//	any iterations that occur above the number of 
		//	nodes in the nodeset should have their
		//	bindings removed.
		long lNodeUBound = m_vertices.GetUpperBound();
		long l;
		for(l = lNodeUBound + 1 ; l <=lUBound ; ++l)
		{
			if(m_Iterations[l])
			{
				m_Iterations[l]->ClearBindings();
				m_Iterations[l]->ConfirmBindings();
			}
		}
	}

	if(m_bIsResizeable)
	{
		long lNewSize = m_vertices.GetSize();
		long lOldSize = m_Iterations.GetSize();
		
		for(long l = lNewSize; l < lOldSize;++l)
		{
			CImplGroup * pGroup =  m_Iterations.GetAt(l);
			if(pGroup)
			{
				pGroup->ClearBindings();
				m_pXForms->m_IterationsToDelete.AddHead(pGroup);
				pGroup->ConfirmBindings();
			}
		}

		if(lOldSize > lNewSize)
			m_Iterations.SetSize(lNewSize);

		HRESULT hr = E_FAIL;
#ifdef AWAIT_NEW_ITERATIONS_MSG
		MSG msg;
		// force msg-q to be created just in case, NOP otherwise
		PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE| PM_NOYIELD);
		// msg to myself to do work
		DWORD dwThread = GetCurrentThreadId();
		const UINT msg_DoPutIterations = WM_APP + 52;
		const UINT msg_CheckIterationCount = WM_APP + 53;
		LPARAM p = (LPARAM)this;
	
		PostThreadMessage(dwThread, msg_DoPutIterations, 0, p);
		// msg-pump

		while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_NOYIELD)) 
		{
			if(msg.message == WM_TIMER)
				break;

			if(msg.hwnd == NULL && msg.message == msg_DoPutIterations && msg.lParam == p)
			{
				hr = static_cast<CXFormsElementRepeat *>(m_pXFormsElement)->put_iterations(lNewSize);
				PostThreadMessage(dwThread, msg_CheckIterationCount, 0, p);
			}
			if (msg.hwnd == NULL && msg.message == msg_CheckIterationCount && msg.lParam == p)
			{
				if(lNewSize == m_Iterations.GetCount())
				{
					break;
				}
				else
				{
					hr = E_FAIL;
				}
			}
			else
			{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
		}
#endif

		if(SUCCEEDED(hr) && m_pXForms->canContinue())
		{
			for(long i = lOldSize; i < lNewSize; ++i)
			{
				try
				{
					m_Iterations[i]->buildDependencies();
				}
				catch(...)
				{
					::AtlTrace("\n! formsPlayer.dll: Failed to build repeat iteration (%i) of repeat '%S'\n",i,m_sId);
					//This could probably go into a messagepump, to await the building of the iteration.
				}
				//m_Iterations[i]->refresh();
			}
		//	refresh();
		}
	}
	
}
// passes the focus through to the current repeat-item
HRESULT CImplRepeat::showFocus(void)
{
	HRESULT hr = FireEvent(EVENT_FOCUS);
	if(SUCCEEDED(hr))
	{
		hr = S_FALSE;
		//Get the current iteration.
		if(m_Iterations.GetSize() > static_cast<long>(m_ulCursor - m_ulScrollOffset - 1))
		{
			CImplGroup * pIteration = 0;
			pIteration = m_Iterations.GetAt(m_ulCursor - m_ulScrollOffset - 1);
			//pass focus on to it.
			if(pIteration)
				pIteration->setFocus();
		}
	}
	return hr;
}
// <summary>checks whether to rebuild the MDT when the index changes</summary>
bool CImplRepeat::rebuildOnIndexChange(void)
{
	bool bRet = m_bCursorRequested;
	/*
	else
	{
		CComQIPtr<IHTMLElement> pElement(m_pXFormsElement->GetControllingUnknown());
		if(pElement)
		{
			CComBSTR sAttrName(L"causesrebuild");
			CComVariant vtResult;
			HRESULT hr = pElement->getAttribute(sAttrName,0,&vtResult);
			switch(vtResult.vt)
			{
				case VT_BSTR:
					if(!wcsicmp(L"false",vtResult.bstrVal))
					{
						bRet = false;
					}	
					break;
				case VT_BOOL:
					if(!vtResult.boolVal)
					{
						bRet = false;
					}
					break;
			}
		}
	}
	*/
	return bRet;
}